package com.yikes.srb.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.IdUtil;
import com.yikes.srb.config.FileProperties;
import com.yikes.srb.service.FileService;
import com.yikes.srb.vo.FileInfoVO;
import com.yikes.srb.web.exception.BizException;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Item;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 *
 * </p>
 *
 * @Author: guanrong.yin
 * @Date: 2024/12/10 23:29
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class FileServiceImpl implements FileService {

    private final MinioClient minioClient;

    protected static final String DATE_FORMAT = "yyyy/MM/dd";

    @Override
    public List<FileInfoVO> uploadFile(MultipartFile[] files) {

        // 存储桶不存在则创建
        createBucketIfAbsent(FileProperties.BUCKET_NAME);

        List<FileInfoVO> list = new ArrayList<>(files.length);
        for (MultipartFile file : files) {
            // 生成文件名(日期文件夹)
            String suffix = FileUtil.getSuffix(file.getOriginalFilename());
            String uuid = IdUtil.simpleUUID();
            String fileName = suffix + StrPool.SLASH + DateUtil.format(LocalDateTime.now(), DATE_FORMAT)
                    + StrPool.C_SLASH + uuid +  StrPool.DOT + suffix;

            //  try-with-resource 语法糖自动释放流
            try (InputStream inputStream = file.getInputStream()) {
                // 文件上传
                PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                        .bucket(FileProperties.BUCKET_NAME)
                        .object(fileName)
                        .contentType(file.getContentType())
                        .stream(inputStream, inputStream.available(), -1)
                        .build();

                minioClient.putObject(putObjectArgs);

                // 返回文件路径
                String fileUrl;

                // 未配置自定义域名
                if (CharSequenceUtil.isBlank(FileProperties.CUSTOM_DOMAIN)) {
                    GetPresignedObjectUrlArgs getPresignedObjectUrlArgs = GetPresignedObjectUrlArgs.builder()
                            .bucket(FileProperties.BUCKET_NAME).object(fileName)
                            .method(Method.GET)
                            .build();

                    fileUrl = minioClient.getPresignedObjectUrl(getPresignedObjectUrlArgs);
                    fileUrl = fileUrl.substring(0, fileUrl.indexOf("?"));

                } else {
                    // 配置自定义文件路径域名
                    fileUrl = FileProperties.CUSTOM_DOMAIN + StrPool.C_SLASH + FileProperties.BUCKET_NAME + StrPool.C_SLASH + fileName;
                }

                FileInfoVO infoVO = new FileInfoVO();
                infoVO.setName(fileName);
                infoVO.setUrl(fileUrl);

                list.add(infoVO);

            } catch (Exception e) {
                throw new BizException("文件上传失败");
            }
        }
        return list;
    }

    @Override
    @SneakyThrows
    public void deleteFile(String filePath) {

        Assert.notBlank(filePath, "删除文件路径不能为空");

        RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder()
                .bucket(FileProperties.BUCKET_NAME)
                .object(filePath)
                .build();
        minioClient.removeObject(removeObjectArgs);
    }

    @Override
    public void downloadFileName(HttpServletResponse response, String fileName) {

        InputStream in = null;
        try {
            StatObjectResponse stat = minioClient.statObject(StatObjectArgs.builder().bucket(FileProperties.BUCKET_NAME).object(fileName).build());

            response.setContentType(stat.contentType());
            response.setHeader("Content-Disposition", "attachment;filename=" +
                    URLEncoder.encode(fileName, String.valueOf(StandardCharsets.UTF_8)));

            //文件下载
            in = minioClient.getObject(GetObjectArgs.builder().bucket(FileProperties.BUCKET_NAME).object(fileName).build());
            in.transferTo(response.getOutputStream());

        } catch (Exception e) {
            log.error(e.getMessage());

        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    log.error(e.getMessage());
                }
            }
        }

    }

    @Override
    public List<Object> listItem() {

        Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder().bucket(FileProperties.BUCKET_NAME).recursive(true).build());

        List<Object> list = new ArrayList<>();
        for (Result<Item> r : results) {
            FileInfoVO info = new FileInfoVO();
            try {
                Item item = r.get();
                info.setName(item.objectName());
                String url = FileProperties.CUSTOM_DOMAIN + StrPool.SLASH + FileProperties.BUCKET_NAME + StrPool.SLASH + item.objectName();
                info.setUrl(url);

                list.add(info);
            } catch (Exception e) {
                log.warn("获取文件列表异常");
            }
        }
        return list;
    }


    /**
     * 创建存储桶(存储桶不存在)
     *
     * @param bucketName
     */
    @SneakyThrows
    private void createBucketIfAbsent(String bucketName) {

        BucketExistsArgs bucketExistsArgs = BucketExistsArgs.builder().bucket(bucketName).build();
        if (!minioClient.bucketExists(bucketExistsArgs)) {

            MakeBucketArgs makeBucketArgs = MakeBucketArgs.builder().bucket(bucketName).build();
            minioClient.makeBucket(makeBucketArgs);

            // 设置存储桶访问权限为PUBLIC， 如果不配置，则新建的存储桶默认是PRIVATE，则存储桶文件会拒绝访问 Access Denied
            SetBucketPolicyArgs setBucketPolicyArgs = SetBucketPolicyArgs
                    .builder()
                    .bucket(bucketName)
                    .config(publicBucketPolicy(bucketName))
                    .build();

            minioClient.setBucketPolicy(setBucketPolicyArgs);
        }
    }

    /**
     * PUBLIC桶策略
     * 如果不配置，则新建的存储桶默认是PRIVATE，则存储桶文件会拒绝访问 Access Denied
     *
     * @param bucketName
     * @return
     */
    private static String publicBucketPolicy(String bucketName) {
        /**
         * AWS的S3存储桶策略
         * Principal: 生效用户对象
         * Resource:  指定存储桶
         * Action: 操作行为
         */
        StringBuilder builder = new StringBuilder();
        builder.append("{\"Version\":\"2012-10-17\"," + "\"Statement\":[{\"Effect\":\"Allow\"," + "\"Principal\":{\"AWS\":[\"*\"]}," + "\"Action\":[\"s3:ListBucketMultipartUploads\",\"s3:GetBucketLocation\",\"s3:ListBucket\"]," + "\"Resource\":[\"arn:aws:s3:::")
                .append(bucketName)
                .append("\"]},").append("{\"Effect\":\"Allow\",")
                .append("\"Principal\":{\"AWS\":[\"*\"]},")
                .append("\"Action\":[\"s3:ListMultipartUploadParts\",\"s3:PutObject\",\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:GetObject\"],")
                .append("\"Resource\":[\"arn:aws:s3:::")
                .append(bucketName)
                .append("/*\"]}]}");

        return builder.toString();
    }

}
